/*
  Handling of IPv4 range lists for implementation of black- and whitelists.

  Copyright (C) 2005 by Peer of the Beer & modifications by Yello

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston,
  MA 02111-1307 USA
*/

#include "osd.h"
#include "config.h"
#include "memalloc.h"
#include "logger.h"
#include "ipv4_list.h"

/* Define the following in order to ignore comment field in the list
   file and therefore spare considerable amount of memory if you have
   a large number of ranges (e.g. guarding.p2p) */
#define IPLIST_IGNORE_DESCRIPTION_FIELD 1

/* The allocation step for list structures.  This can be anything over
   1, but anything less than couple thousands makes reading of large
   listfile slower. */
#define IPLIST_ITEM_ALLOC_STEP 65536

typedef struct ipv4_rangelist_item_s {
#ifndef IPLIST_IGNORE_DESCRIPTION_FIELD
	char *description;
#endif /* ! IPLIST_IGNORE_DESCRIPTION_FIELD */
	uint32_t range_start;
	uint32_t range_end;
} ipv4_rangelist_item_t;

static ipv4_rangelist_item_t *white_list = NULL;
static size_t white_list_items = 0;
static size_t white_list_alloc = 0;

static ipv4_rangelist_item_t *black_list = NULL;
static size_t black_list_items = 0;
static size_t black_list_alloc = 0;

static int list_read(const char *file_name,
                     ipv4_rangelist_item_t **list,
                     size_t *list_items,
                     size_t *list_alloc);

static int list_match(uint32_t addr,
                      ipv4_rangelist_item_t **list,
                      size_t *list_items,
                      size_t *list_alloc,
                      char **descr_ret);

static void ipv4_clear_list(ipv4_rangelist_item_t **list,
                            size_t *list_items,
                            size_t *list_alloc);

static int parse_list_line(char *line,
                           ipv4_rangelist_item_t *list);

static int item_compare(const void *item1, const void *item2);

int ipv4_white_list_read(const char *file_name)
{
	int rv;

	FUN("ipv4_white_list_read");

	rv = list_read(file_name,
		&white_list, &white_list_items, &white_list_alloc);

	return rv;
}

int ipv4_black_list_read(const char *file_name)
{
	int rv;

	FUN("ipv4_black_list_read");

	rv = list_read(file_name,
                    &black_list, &black_list_items, &black_list_alloc);

     return rv;
}

int ipv4_white_list_match(uint32_t addr, char **descr_ret)
{
	int rv;

	FUN("ipv4_white_list_match");

	rv = list_match(addr, 
		&white_list, &white_list_items, &white_list_alloc, descr_ret);

	return rv;
}

int ipv4_black_list_match(uint32_t addr, char **descr_ret)
{
	int rv;

	 FUN("ipv4_black_list_match");

	rv = list_match(addr, 
		&black_list, &black_list_items, &black_list_alloc, descr_ret);

	return rv;
}

void ipv4_clear_lists()
{
	FUN("ipv4_clear_lists");

	ipv4_clear_list(&white_list, &white_list_items, &white_list_alloc);
	ipv4_clear_list(&black_list, &black_list_items, &black_list_alloc);
}

static int list_read(const char *file_name,
                     ipv4_rangelist_item_t **list,
                     size_t *list_items,
                     size_t *list_alloc)
{
	FILE *f;
	size_t i, j, lineno;
	char line[256], *hlp;
	int ooo = 0;

	FUN("list_read");

#ifndef IPLIST_IGNORE_DESCRIPTION_FIELD
	for (i = 0; i < *list_items; i++) {
		if ((*list)[i].description != NULL) {
			free((*list)[i].description);
			(*list)[i].description = NULL;
		}
	}
#endif /* ! IPLIST_IGNORE_DESCRIPTION_FIELD */
	*list_items = 0;

	f = fopen(file_name, "r");
	if (f == NULL) {
		LOGS(L_PEER,L_ERROR,
			("Can't open ip range file \"%s\".\n", file_name));
		return -1;
	}

	i = 0;
	lineno = 0;
	while(fgets(line, sizeof (line), f) != NULL) {
		lineno++;
		while (((hlp = strchr(line, '\n')) != NULL) ||
			((hlp = strchr(line, '\r')) != NULL)) {
			*hlp = '\0';
		}
		if (*line == '#') {
			/* ignore comment lines */
			continue;
		}
		if (i >= *list_alloc) {
			ipv4_rangelist_item_t *nl;

			nl = (ipv4_rangelist_item_t *)
				xcalloc(*list_alloc + IPLIST_ITEM_ALLOC_STEP,
					sizeof (*nl));
			if (*list != NULL) {
				for (j = 0; j < i; j++) {
					nl[j] = (*list)[j];
				}
				xfree(*list);
			}
			*list = nl;
			*list_alloc += IPLIST_ITEM_ALLOC_STEP;
		}
		if (parse_list_line(line, &(*list)[i])) {
			if (((*list)[i]).range_start > ((*list)[i]).range_end) {
				LOGS(L_PEER,L_ERROR,
					("Ignored empty range \"%s\" on line %d in iplist.\n",
					line, lineno));
			} else if ((i > 0) &&
				(item_compare(&((*list)[i - 1]), &((*list)[i])) > 0)) {
				/* Range is out of order and the array needs to be
				 * sorted afterwards
				 */
				ooo = 1;
				i++;
			} else {
				i++;
			}
		} else {
			LOGS(L_PEER,L_ERROR,
				("Invalid line \"%s\" on line %d in iplist.\n",
				line, lineno));
		}
	}
	fclose(f);

	if (ooo) {
		LOGS(L_PEER,L_MINOR, ("Range file is out of order, sorting data.\n"));
		qsort(*list, i, sizeof (ipv4_rangelist_item_t), item_compare);
	}

	*list_items = i;

	/* give some feedback */
	info("%d entries ", i);

	return *list_items;
}

static int item_compare(const void *item1, const void *item2)
{
	ipv4_rangelist_item_t *i1 = (ipv4_rangelist_item_t *)item1;
	ipv4_rangelist_item_t *i2 = (ipv4_rangelist_item_t *)item2;

	FUN("item_compare");

	if (i1->range_start < i2->range_start) {
		return -1;
	} else if (i1->range_start > i2->range_start) {
		return 1;
	}
	if (i1->range_end < i2->range_end) {
		return -1;
	} else if (i1->range_end > i2->range_end) {
		return 1;
	}
	return 0;
}

static int list_match(uint32_t addr,
                      ipv4_rangelist_item_t **list,
                      size_t *list_items,
                      size_t *list_alloc,
                      char **descr_ret)
{
	size_t i;
	uint32_t haddr;
	int rv = 0;

	FUN("list_match");

	/* avoid unused warning */
	(void)list_alloc;

	haddr = ntohl(addr);

	/*
	 * We could do some kind of modified binary search here, but this
	 * seems to take less than a millisecond in pentium with this
	 * primitive linear search with blacklist of more than 100000
	 * ranges, so I won't bother for now.
	 */
	for (i = 0; i < *list_items; i++) {
		if ((haddr >= ((*list)[i]).range_start) &&
			(haddr <= ((*list)[i]).range_end)) {
			rv = 1;
			goto out;
		}
		if (haddr < ((*list)[i]).range_start) {
			/*
			 * Since the list is ordered, all following ranges
			 * start above the address we are looking for.
			 */
			goto out;
		}
	}

out:
	if (descr_ret != NULL) {
#ifndef IPLIST_IGNORE_DESCRIPTION_FIELD
		if (rv != 0) {
			*descr_ret = ((*list)[i]).description;
		}
#else /* ! IPLIST_IGNORE_DESCRIPTION_FIELD */
		*descr_ret = NULL;
#endif /* ! IPLIST_IGNORE_DESCRIPTION_FIELD */
	}
	return rv;
}

static void ipv4_clear_list(ipv4_rangelist_item_t **list,
                            size_t *list_items,
                            size_t *list_alloc)
{
#ifndef IPLIST_IGNORE_DESCRIPTION_FIELD
	size_t i;
#endif

	FUN("ipv4_clear_list");

#ifndef IPLIST_IGNORE_DESCRIPTION_FIELD
	for (i = 0; i < *list_items; i++) {
		if ((*list)[i].description != NULL) {
			xfree((*list)[i].description);
			(*list)[i].description = NULL;
		}
	}
#endif /* ! IPLIST_IGNORE_DESCRIPTION_FIELD */
	if (*list_alloc != 0) {
		xfree(*list);
	}
	*list_alloc = 0;
	*list_items = 0;
	*list = NULL;
}

static int parse_list_line(char *line, ipv4_rangelist_item_t *item)
{
	char *colon = NULL, *dash = NULL;
	char *descr = NULL, *ip1 = NULL, *ip2 = NULL;
	int rv = 0;
	struct in_addr in_addr;

	FUN("parse_list_line");

	descr = line;

	colon = strrchr(descr, ':');
	if (colon == NULL) {
		/* Format error. */
		goto out;
	}
	*colon = '\0';

	ip1 = colon + 1;

	dash =  strchr(ip1, '-');
	if (dash == NULL) {
		/* Format error. */
		goto out;
	}
	*dash = '\0';

	ip2 = dash + 1;

	memset(&in_addr, 0, sizeof (in_addr));
	if (inet_aton(ip1, &in_addr) == 0) {
		/* Malformed ip address. */
		goto out;
	}
	item->range_start = ntohl(in_addr.s_addr);

	memset(&in_addr, 0, sizeof (in_addr));
	if (inet_aton(ip2, &in_addr) == 0) {
		/* Malformed ip address. */
		goto out;
	}
	item->range_end = ntohl(in_addr.s_addr);

#ifndef IPLIST_IGNORE_DESCRIPTION_FIELD
	if (strlen(descr) > 0) {
		item->description = xstrdup(descr);
	} else {
		item->description = NULL;
	}
#endif /* ! IPLIST_IGNORE_DESCRIPTION_FIELD */

	rv = 1;

out:
	if (colon != NULL)
		*colon = ':';
	if (dash != NULL)
		*dash = '-';
	if (rv == 0) {
		memset(item, 0, sizeof (*item));
	}
	return rv;
}

